package com.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.lotterysystem.common.config.ExecutorConfig;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.MailUtil;
import com.example.lotterysystem.common.utils.SMSUtil;
import com.example.lotterysystem.controller.param.DrawPrizeParam;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.WinningRecordDO;
import com.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.example.lotterysystem.dao.mapper.WinningRecordMapper;
import com.example.lotterysystem.service.DrawPrizeService;
import com.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    @Qualifier("asyncServiceExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @RabbitHandler
    public void process(Map<String,String> messages) throws Exception {
        //成功接收到队列中的消息
        logger.info("MQ成功接收到消息 message:{}", JacksonUtil.writeValueAsString(messages));
        String paramString = messages.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖的流程
        try {
            //校验抽奖请求是否有效
            //1.有可能前端发起两个一样的抽奖请求, 对于param来说也是一样的两个请求
            //2.param:最后一个奖项
            //  处理param1: 活动完成, 奖品完成
            //  处理param2: 回滚
            if(!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }
            //活动, 奖品, 人员的状态 状态扭转处理
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList =
                    drawPrizeService.saveWinnerRecords(param);
            //通知中奖者(邮箱,短信) 根据中奖信息
//            syncExecute(winningRecordDOList);
        }catch (ServiceException e) {
            logger.error("处理 MQ 消息异常!{}:{}", e.getCode(), e.getMessage());
            //需要保证事务的一致性(回滚)
            rollback(param);
            // 抛出异常:消息重试 (解决异常:代码bug, 网络问题, 服务问题) 消息自动转入到死信队列
            throw e;
        }catch (Exception e) {
            logger.error("处理 MQ 消息异常!", e);
            //需要保证事务的一致性(回滚)
            rollback(param);
            // 抛出异常:消息重试 (解决异常:代码bug, 网络问题, 服务问题) 消息自动转入到死信队列
            throw e;
        }
    }

    /**
     * 处理抽奖异常的回滚行为:恢复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {

        //1.回滚状态:活动, 奖品, 人员
        // 判断状态是否需要回滚
        if(!statusNeedRollBack(param)) {
            // 不需要 return
            return;
        }
        // 需要回滚
        rollbackStatus(param);

        //2.回滚中奖者名单
        // 判断状态是否需要回滚
        if(!winnerNeedRollBack(param)) {
            // 不需要 return
            return;
        }
        // 需要回滚
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录, 删除奖品下的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollBack(DrawPrizeParam param) {
        //判断活动中的奖品是否存在中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());

        return count > 0;
    }

    /**
     * 恢复相关状态
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 设计状态的恢复工作, 使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId).collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollBack(DrawPrizeParam param) {
        // 判断活动+奖品+人员相关状态是否已经扭转
        // 扭转状态时, 保证了事务一致性, 要么都扭转了, 要么都没扭转(不包含活动)
        // 因此只要判断人员/奖品是否扭转过, 就能判断出状态是否全部扭转
        // 不能判断活动是否已经扭转
        ActivityPrizeDO activityPrizeDO
                = activityPrizeMapper.selectByActivityPrizeId(param.getActivityId(), param.getPrizeId());

        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETE.name())) {
            //已经扭转了, 需要回滚
            return true;
        }
        return false;
    }

    /**
     * 并发处理抽奖后续流程
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //通过线程池 threadPoolTaskExecutor

        //短信通知
        threadPoolTaskExecutor.execute(()->{
            sendMessage(winningRecordDOList);
        });

        //邮件通知
        threadPoolTaskExecutor.execute(()->{
            sendMail(winningRecordDOList);
        });

    }

    /**
     * 发送邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空, 不用发邮件!");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList) {
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    "hi, " + winningRecordDO.getWinnerName()
                            + "，恭喜您在"+ winningRecordDO.getActivityName()
                            + "活动中获得" + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                            + ", 您获得的奖品是"+ winningRecordDO.getPrizeName()
                            + "。获奖时间为 "+ DateUtil.formatTime(winningRecordDO.getWinningTime())
                            +"，请尽快领取奖品！");
        }

    }

    /**
     * 发短信
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空, 不用发短信!");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String,String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));

            smsUtil.sendMessage("SMS_473330293",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETE);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETE);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner :: getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETE);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

    /**
     * 状态扭转
     * @param param
     */
//    private void statusConvert(DrawPrizeParam param) {
//        //活动: RUNNING-->COMPLETE 全部奖品抽取完之后才改变状态
//        //奖品: INIT-->COMPLETE
//        //人员列表: INIT-->COMPLETE
//
//        //问题:
//        //1.活动状态扭转有依赖性, 导致代码维护较差
//        //2.状态扭转条件可能会扩展, 当前写法, 扩展性差, 维护性差
//
//        //1.扭转奖品状态
//        //查询活动关联奖品信息
//        //条件判断是否符合扭转奖品状态, 判断当前状态是否是COMPLETE, 如果是, 不要扭转
//        //判断通过才扭转
//
//        //2.扭转人员状态
//        //查询活动关联人员信息
//        //条件判断是否符合扭转人员状态, 判断当前状态是否是COMPLETE, 如果是, 不要扭转
//        // 判断通过才扭转
//
//        //3.扭转活动状态 (必须在扭转奖品状态之后完成)
//        //查询活动信息
//        //条件判断是否符合扭转活动状态, 判断当前状态是否是COMPLETE, 如果不是, 且全部奖品抽完之后才改变状态
//        // 判断通过才扭转
//
//        //4.更新活动完整信息缓存
//
//    }
}
